﻿using System.Collections.Immutable;
using System.Diagnostics;
using DimensionsHelper.Common.Utils;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService.Symbols;

/// <summary>
///     元数据字段类型，对应不同的声明关键字，如：Text，Categorical等
/// </summary>
public enum MetadataFieldKind
{
    /// <summary>
    ///     使用define关键字定义元数据分类列表
    /// </summary>
    ElementList,

    /// <summary>
    ///     使用info关键字定义的元数据字段。
    ///     此类字段只用于出示信息，不保存任何类型数据。
    /// </summary>
    Info,

    /// <summary>
    ///     使用block关键字定义的字段块，
    ///     本身不保存数据，在块内部保存各种字段，拥有独立的命名空间。
    ///     不能在内部声明<see cref="ElementList" />分类元素列表。
    /// </summary>
    Block,

    /// <summary>
    ///     使用page关键字定义的页面字段。此类字段不保存数据，内部保存
    ///     页面包含的变量列表，其中的字段名必须是已经定义的字段。
    /// </summary>
    Page,

    /// <summary>
    ///     使用loop关键字定义的带有分类列表的循环字段。
    ///     本身不保存数据据，在fields语句块中定义子字段，其中
    ///     允许定义分类元素列表、嵌套循环字段和其他非循环字段。
    /// </summary>
    CategoricalLoop,

    /// <summary>
    ///     使用loop关键字定义的没有分类字段的循环字段，允许定义数值范围。
    ///     本身不保存数据据，在fields语句块中定义子字段，其中
    ///     允许定义分类元素列表、嵌套循环字段和其他非循环字段。
    /// </summary>
    NumericLoop,

    /// <summary>
    ///     使用compound关键字定义的循环字段，和<see cref="CategoricalLoop" />
    ///     不同的是，compound的循环层和子分类字段具有相同的分类列表。且无需在循环层
    ///     进行声明。
    /// </summary>
    Compound,

    /// <summary>
    ///     使用categorical关键字定义的分类选择字段。允许保存一个或多个值，
    ///     可以使用数值范围定义。
    /// </summary>
    Categorical,

    /// <summary>
    ///     使用boolean关键字定义的布尔值字段，保存true或false。
    /// </summary>
    Boolean,

    /// <summary>
    ///     使用date关键字定义的日期字段，保存整数或字符串格式的日期值。
    /// </summary>
    Date,

    /// <summary>
    ///     使用double关键字定义的双精度浮点数字段，保存整数或双精度浮点数。
    /// </summary>
    Double,

    /// <summary>
    ///     使用long关键字定义的长整型整数字段，保存32位带符号的整数值。
    /// </summary>
    Long,

    /// <summary>
    ///     使用text关键字定义的字符串字段，保存字符串值。
    /// </summary>
    Text
}


/// <summary>
///     元数据字段符号，不同类型的元数据字段都是用此类型声明。当原始字段带有子字段时，子字段将会
///     保存在字段表中，拥有单独的命名空间。字段全名类似模块的全限定名，使用'.'区分各级字段名。
/// </summary>
public sealed class MetadataFieldSymbol : MetadataLabeledObjectSymbol, IBindableSymbol
{
    private static ITypeSymbol? _cachedBindingSymbol;
    private static string _bindingTypeName = "DOM.IQuestion";
    private readonly List<IDeclaration> _declarations;


    internal readonly MetadataClassSymbol? Class;


    private int _cachedCategoriesCount = -1;

    private SymbolTable? _dbPropertySymbols;
    private Dictionary<string, ElementListRef>? _referencedElementLists;

    internal MetadataFieldSymbol(MetadataField fieldDeclaration, MetadataFieldSymbol? containingSymbol)
        : base(SymbolKinds.MetadataField, containingSymbol, fieldDeclaration)
    {
        Name = fieldDeclaration.Name;
        ContainingSymbol = containingSymbol;
        Declaration = fieldDeclaration;
        LocationMap.AddLocation(fieldDeclaration.GetLocation());

        FieldKind = MetadataFieldKind.Info;
        if (fieldDeclaration.IsElementList)
        {
            FieldKind = MetadataFieldKind.ElementList;
        }
        else if (fieldDeclaration.IsPage)
        {
            FieldKind = MetadataFieldKind.Page;
        }
        else if (fieldDeclaration.IsBlock)
        {
            FieldKind = MetadataFieldKind.Block;
        }
        else if (fieldDeclaration.IsLoop)
        {
            if ((fieldDeclaration.Categories == null ||
                 fieldDeclaration.Categories.Count == 0) &&
                fieldDeclaration.ValueRanges != null)
            {
                FieldKind = MetadataFieldKind.NumericLoop;
            }
            else
            {
                FieldKind = MetadataFieldKind.CategoricalLoop;
            }
        }
        else if (fieldDeclaration.IsComment)
        {
            FieldKind = MetadataFieldKind.Compound;
        }
        else if (fieldDeclaration.IsCategorical)
        {
            FieldKind = MetadataFieldKind.Categorical;
        }
        else if (fieldDeclaration.IsBoolean)
        {
            FieldKind = MetadataFieldKind.Boolean;
        }
        else if (fieldDeclaration.IsDate)
        {
            FieldKind = MetadataFieldKind.Date;
        }
        else if (fieldDeclaration.IsDouble)
        {
            FieldKind = MetadataFieldKind.Double;
        }
        else if (fieldDeclaration.IsLong)
        {
            FieldKind = MetadataFieldKind.Long;
        }
        else if (fieldDeclaration.IsText)
        {
            FieldKind = MetadataFieldKind.Text;
        }

        _declarations = [];

        if (fieldDeclaration.Class != null)
        {
            Class = new MetadataClassSymbol(this, fieldDeclaration.Class);
        }
    }


    internal DomainSymbolTable? FieldSymbols => Class?.Fields;


    /// <summary>
    ///     元数据的字段类型。不是所有字段都可以保存数据。
    /// </summary>
    public MetadataFieldKind FieldKind { get; }

    public override MetadataField Declaration { get; }


    /// <summary>
    ///     获取分类定义符号的个数
    /// </summary>
    public int CategoriesCount
    {
        get
        {
            if (_cachedCategoriesCount >= 0)
            {
                return _cachedCategoriesCount;
            }

            int result = CategoriesNullable?.Count ?? 0;
            _referencedElementLists?.Values.ForEach(listRef => result += listRef.List.CategoriesCount);
            return _cachedCategoriesCount = result;
        }
    }


    public override string Name { get; }

    public ImmutableArray<IDeclaration> GetDeclarations()
    {
        return [.._declarations];
    }

    public ITypeSymbol BindingSymbol => GetBindingSymbol();


    public new ITypeSymbol? ContainingSymbol { get; }


    /// <summary>
    ///     查找当前字段下的指定名称的子字段。
    /// </summary>
    /// <param name="name">子字段名，不区分大小写</param>
    /// <param name="searchParent">此参数无效</param>
    public ITypeSymbol? LookupNamedMember(string name, bool searchParent)
    {
        return Class?.LookupField(name, searchParent);
    }


    /// <summary>
    ///     查找当前字段下的指定名称的子字段。
    /// </summary>
    /// <param name="identifier">指定标识符</param>
    public ITypeSymbol? LookupNamedMember(Identifier identifier)
    {
        return LookupNamedMember(identifier.Value, false);
    }


    public TypeKinds TypeKind => TypeKinds.MetadataField;

    public ITypeSymbol? BaseType => null;

    public IReadOnlyList<ITypeSymbol> Interfaces => new List<ITypeSymbol>();

    public string? ContainingModuleName => null;

    public ISymbol? ContainingModule => null;

    public bool IsUnion => false;

    public bool IsModule => false;

    public bool IsClass => false;

    public bool IsInterface => false;

    public bool IsEnum => false;

    public bool IsDefault => false;

    public bool IsReadOnly => false;

    public bool IsWriteOnly => false;

    public bool IsSignature => false;

    public bool IsNullable => true;

    public bool IsBindable => true;

    public bool WithParameter => false;

    public bool WithConstant => false;


    public ImmutableArray<ISymbol> GetMembers()
    {
        return FieldSymbols?.ToImmutableArray() ?? ImmutableArray<ISymbol>.Empty;
    }

    public ITypeSymbol? LookupNamedMember(string name)
    {
        return LookupNamedMember(name, false);
    }

    public ITypeSymbol? GetDefaultProperty()
    {
        return null;
    }

    public ITypeSymbol? GetDefaultMethod()
    {
        return null;
    }

    public ITypeSymbol? GetIndexer()
    {
        return null;
    }

    public bool IsAssignableFrom(ITypeSymbol type)
    {
        if (FieldKind is MetadataFieldKind.Block or MetadataFieldKind.CategoricalLoop
            or MetadataFieldKind.NumericLoop or MetadataFieldKind.Compound or MetadataFieldKind.ElementList
            or MetadataFieldKind.Page or MetadataFieldKind.Info)
        {
            return false;
        }

        if (type.IsValueType)
        {
            if (ReferenceEquals(type, ValueTypeSymbol.Null))
            {
                return true;
            }

            switch (FieldKind)
            {
                case MetadataFieldKind.Categorical:
                    return ReferenceEquals(type, ValueTypeSymbol.Categorical);
                case MetadataFieldKind.Text:
                    return ReferenceEquals(type, ValueTypeSymbol.String);
                case MetadataFieldKind.Boolean:
                    return ReferenceEquals(type, ValueTypeSymbol.Boolean);
                case MetadataFieldKind.Long:
                    return ReferenceEquals(type, ValueTypeSymbol.Long);
                case MetadataFieldKind.Date:
                    return ReferenceEquals(type, ValueTypeSymbol.Date) ||
                           ReferenceEquals(type, ValueTypeSymbol.String) ||
                           ReferenceEquals(type, ValueTypeSymbol.Long);
            }
        }

        return type is MetadataFieldSymbol fieldSymbol && fieldSymbol.FieldKind == FieldKind;
    }

    public bool HasInterface(ITypeSymbol symbol)
    {
        return false;
    }

    public ITypeSymbol MakeArrayType(int rank, int[]? boundaries)
    {
        throw new NotSupportedException();
    }

    public override bool IsMetadata => true;

    public override bool IsMetadataField => true;


    public override bool Redeclare(ISymbol symbol)
    {
        if (symbol is not MetadataFieldSymbol fieldSymbol ||
            LocationMap.FileExists(fieldSymbol.Declaration.Uri))
        {
            return false;
        }

        _declarations.Add(fieldSymbol.Declaration);
        base.Redeclare(symbol);
        return Join(fieldSymbol);
    }


    /// <summary>
    ///     设置在<see cref="Binder" />绑定元数据字段符号时使用的全局声明符号类型。
    ///     默认是'DOM.IQuestion'。
    /// </summary>
    /// <param name="bindingTypeName">绑定的类型全限定名</param>
    public static void SetBindingTypeName(string bindingTypeName)
    {
        if (bindingTypeName.Equals(_bindingTypeName, StringComparison.OrdinalIgnoreCase))
        {
            return;
        }

        _bindingTypeName = bindingTypeName;
        _cachedBindingSymbol = null;
    }


    /// <summary>
    ///     获取当前设置的元数据字段绑定的声明类型。
    /// </summary>
    public static ITypeSymbol? GetBindingTypeSymbol()
    {
        return _cachedBindingSymbol ??= GlobalSymbols.LookupDeclaredSymbol(_bindingTypeName) as ITypeSymbol;
    }


    private static ITypeSymbol GetBindingSymbol()
    {
        if (_cachedBindingSymbol != null)
        {
            return _cachedBindingSymbol;
        }

        if (GlobalSymbols.TryGetDeclaredSymbol(_bindingTypeName, out ISymbol? symbol) &&
            symbol is ITypeSymbol typeSymbol)
        {
            return _cachedBindingSymbol = typeSymbol;
        }

        return ValueTypeSymbol.Any;
    }


    /// <summary>
    ///     将给定符号声明到子字段符号表中。
    /// </summary>
    /// <param name="subField">需要声明的字段符号</param>
    /// <returns>如果给定字段名已存在，返回<see langword="false" /></returns>
    internal bool DeclareNamedMember(MetadataFieldSymbol subField)
    {
        return Class?.DeclareField(subField) ?? false;
    }


    /// <summary>
    ///     获取字段值范围定义列表
    /// </summary>
    public MetadataFieldValueRangeList? GetFieldValueRanges()
    {
        return Declaration.ValueRanges;
    }


    private ISymbol? LookupCategoryWithoutNamespace(string name)
    {
        ISymbol? category = CategoriesNullable?.LookupLocalSymbol(name);

        if (category != null)
        {
            return category;
        }

        category = _referencedElementLists?.Values.Select(listRef =>
        {
            ISymbol? found = listRef.List.LookupCategory(name);

            if (found != null && listRef.WithNamespace)
            {
                return null;
            }

            return found;
        }).OfType<MetadataCategorySymbol>().FirstOrDefault();

        return category;
    }


    /// <summary>
    ///     查找指定名称的分类符号，此方法支持使用全限定名在子列表中查找。
    /// </summary>
    /// <param name="name">分类名，不区分大小写</param>
    public ISymbol? LookupCategory(string name)
    {
        string[] qualified = name.Split('.');
        MetadataCategorySymbol? category = null;
        foreach (string? member in qualified)
        {
            if (category == null)
            {
                category = CategoriesNullable?.LookupLocalSymbol(member) as MetadataCategorySymbol ??
                           _referencedElementLists?.Select(pair => pair.Value.List.LookupCategory(name))
                               .OfType<MetadataCategorySymbol>().FirstOrDefault();
            }
            else
            {
                // 只有带有 namespace 关键字的情况下，才允许使用全限定名
                if (category.HasNamespace)
                {
                    category = category.LookupLocalCategory(member);
                }
                else
                {
                    return null;
                }
            }

            if (category == null)
            {
                break;
            }
        }

        return category;
    }


    /// <summary>
    ///     声明一个分类元素符号，如果分类元素名冲突，将返回<see langword="false" />。
    ///     此处不检查字段类型是否兼容分类元素列表。
    /// </summary>
    /// <param name="categorySymbol">需要声明的分类元素符号</param>
    internal bool DeclareCategory(MetadataCategorySymbol categorySymbol)
    {
        bool result = LookupCategoryWithoutNamespace(categorySymbol.Name) == null
                      && GetCategorySymbols().DeclareSymbol(categorySymbol);

        // 更新个数
        if (result)
        {
            _cachedCategoriesCount++;
        }

        return result;
    }


    /// <summary>
    ///     添加一个引用的分类元素列表，如果指定名称的引用已存在，返回<see langword="false" />。
    /// </summary>
    /// <param name="elementListSymbol">引用的分类元素列表</param>
    /// <param name="withNamespace">是否在引用时添加了<see cref="SyntaxKinds.NamespaceKeywordToken" /></param>
    public bool ReferenceElementList(MetadataFieldSymbol elementListSymbol, bool withNamespace)
    {
        Debug.Assert(elementListSymbol.FieldKind == MetadataFieldKind.ElementList);
        if (_referencedElementLists?.ContainsKey(elementListSymbol.Name) == true)
        {
            return false;
        }

        _referencedElementLists ??= new Dictionary<string, ElementListRef>(StringComparer.OrdinalIgnoreCase);
        _referencedElementLists.Add(elementListSymbol.Name,
            new ElementListRef(elementListSymbol, withNamespace));
        _cachedCategoriesCount += elementListSymbol.CategoriesCount;
        return true;
    }


    /// <summary>
    ///     查找指定名称的数据库属性定义。
    /// </summary>
    /// <param name="name">属性名，不区分大小写</param>
    public ISymbol? FindDbProperty(string name)
    {
        return _dbPropertySymbols?.LookupLocalSymbol(name);
    }


    internal void DeclareDbProperty(MetadataDbPropertySymbol propertySymbol)
    {
        _dbPropertySymbols ??= new DomainSymbolTable(this);
        _dbPropertySymbols.DeclareSymbol(propertySymbol);
    }


    private bool Join(MetadataFieldSymbol fieldSymbol)
    {
        // 检查字段类型是否兼容
        if (FieldKind != fieldSymbol.FieldKind)
        {
            return false;
        }

        // 合并分类列表
        fieldSymbol.CategoriesNullable?.ForEach(symbol =>
        {
            if (symbol is not MetadataCategorySymbol categorySymbol)
            {
                return;
            }

            GetCategorySymbols().DeclareSymbol(categorySymbol);
        });

        // 合并字段列表
        fieldSymbol.FieldSymbols?.ForEach(symbol =>
        {
            if (symbol is not MetadataFieldSymbol field)
            {
                return;
            }

            FieldSymbols?.DeclareSymbol(field);
        });

        return base.Redeclare(fieldSymbol);
    }


    private record ElementListRef(MetadataFieldSymbol List, bool WithNamespace);
}